package com.auto.finance.auth.collect;

import com.auto.finance.auth.collect.annotations.ApiExclude;
import com.auto.finance.auth.collect.annotations.ApiInclude;
import com.auto.finance.auth.collect.beans.ApiEntity;
import com.auto.finance.auth.collect.beans.ApiInfo;
import com.auto.finance.auth.collect.support.HttpResult;
import com.auto.finance.auth.collect.support.Reporter;
import com.auto.finance.auth.collect.support.RequestPathHandleAdapter;
import com.auto.finance.auth.collect.support.Result;
import com.eairlv.utils.annotation.AccessPermission;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;

/**
 * @author tianye
 */
@Accessors(chain = true)
@Slf4j
public class ApiCollectService implements ApplicationListener<ContextRefreshedEvent> {

    private volatile boolean start = false;

    private final RequestPathHandleAdapter requestPathHandleAdapter = new RequestPathHandleAdapter();

    @Setter
    private CollectProperties properties;

    @Value("${spring.application.name}")
    private String module;

    @Autowired
    @Qualifier(CollectAutoConfiguration.REPORT_THREAD_POOL_NAME)
    private ExecutorService executorService;

    @Autowired
    private Reporter reporter;


    private void collect() {
        log.info("[auth collect] Start to collect api");
        Set<Class> apiInterfaces = new HashSet<>();
        Reflections reflections = new Reflections(properties.getBasePackage());
        // @RestController
        apiInterfaces.addAll(reflections.getTypesAnnotatedWith(RestController.class));
        // @Controller
        apiInterfaces.addAll(reflections.getTypesAnnotatedWith(Controller.class));

        Set<ApiInfo> set = new HashSet<>();
        for (Class cls : apiInterfaces) {
            for (ApiInfo info : buildApiEntity(cls)) {
                if (!set.add(info)) {
                    throw new RuntimeException("[auth collect] @ApiInclude: ApiId repeat, Class [" + cls.getCanonicalName() + "], ApiId [" + info.getApiId() + "]");
                }
            }
        }
        log.info("[auth collect] Api collect finished, size: " + set.size());

        if (set.size() == 0) {
            return;
        }

        ApiEntity entity = new ApiEntity().setModule(module).setList(new ArrayList<>(set));
        log.info("[auth collect] Ready to start sending data to auth-service");

        executorService.execute(() -> {
            Result response = null;
            boolean again = false;
            do {
                try {
                    if (again) {
                        log.debug("[auth collect] Resource report fail, try to send again !!");
                        Thread.sleep(properties.getRetryTime());
                    }
                    response = reporter.reportApiCollect(entity);
                    if (response.getCode() == HttpResult.SUCCESS.getCode()) {
                        log.info("[auth collect] Send finished");
                    }
                    again = true;
                } catch (Exception e) {
                    again = true;
                }
            } while (response == null || response.getCode() != HttpResult.SUCCESS.getCode());
        });

    }


    private List<ApiInfo> buildApiEntity(Class cls) {
        String[] preFixPath = null;
        Annotation preFixPathAnnotation = cls.getAnnotation(RequestMapping.class);
        if (null != preFixPathAnnotation) {
            RequestMapping preFixMapping = (RequestMapping) preFixPathAnnotation;
            preFixPath = preFixMapping.value();
        }

        Method[] methods = cls.getMethods();
        List<ApiInfo> result = new ArrayList<>();
        for (Method method : methods) {
            if (exclude(method)) {
                continue;
            }
            Annotation ano = null;
            Class[] annotions = {RequestMapping.class, GetMapping.class, PostMapping.class, PutMapping.class, DeleteMapping.class, PatchMapping.class};
            loop:
            for (Class c : annotions) {
                if ((ano = method.getAnnotation(c)) != null) {
                    ApiInclude apiInclude = method.getAnnotation(ApiInclude.class);
                    result.addAll(requestPathHandleAdapter.handle(preFixPath, ano, apiInclude));
                    break loop;
                }
            }
        }
        return result;
    }

    private boolean exclude(Method method) {
        if (null != method.getAnnotation(ApiExclude.class)) {
            return true;
        }

        if (null != method.getAnnotation(ApiInclude.class)) {
            return false;
        }

        AccessPermission accessPermission = method.getAnnotation(AccessPermission.class);
        if (null == accessPermission) {
            return true;
        }
        if (!accessPermission.jwt()) {
            return true;
        }
        return false;
    }

    @Override
    public synchronized void onApplicationEvent(ContextRefreshedEvent event) {
        if (properties.isEnable() && !start) {
            start = true;
            collect();
        }
    }
}
